/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.context.event;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.ErrorHandler;

/**事件发布的核心流程：
 * <br/>1、程序启动到某个步骤之后，调用EventPublishingRunListener的某个方法
 * <br/>2、EventPublishingRunListener的具体方法将application参数和args参数封装到对应的事件中。这里的事件均为SpringApplicationEvent的实现类
 * <br/>3、通过成员变量initialMulticaster的multicastEvent方法对事件进行广播，或通过该方法的ConfigurableApplicationContext参数的publishEvent方法来对事件进行发布
 * <br/>4、对应的ApplicationListener被触发，执行相应的业务逻辑
 * <br/>{@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
 * <p>
 * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
 * before the context is actually refreshed.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @since 1.0.0
 */
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

	private final SpringApplication application;

	private final String[] args;

	private final SimpleApplicationEventMulticaster initialMulticaster;

	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		// 创建SimpleApplicationEventMulticaster广播器
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		// 遍历ApplicationListener并关联SimpleApplicationEventMulticaster
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	@SuppressWarnings("deprecation")
	public void starting() {
		this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.application, this.args));
	}

	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {

	}

	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		// 遍历application中的所有监听器实现类
		for (ApplicationListener<?> listener : this.application.getListeners()) {
			// 如果为ApplicationContextAware，则将上下文信息设置到该监听器内
			if (listener instanceof ApplicationContextAware) {
				((ApplicationContextAware) listener).setApplicationContext(context);
			}
			// 将application中的监听器实现类全部添加到上下文中
			context.addApplicationListener(listener);
		}
		// ConfigFileApplicationListener会再增加一个beanFactoryPostProcessor: org.springframework.boot.context.config.ConfigFileApplicationListener$PropertySourceOrderingPostProcessor@10ded6a9
		// 广播事件ApplicationPreparedEvent，注意这个方法是一个分水岭，在它之后的事件，由于上下文已经初始化完成，可以通过context的publishEvent来进行事件的发布
		this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
	}

	@Override
	public void finished(ConfigurableApplicationContext context, Throwable exception) {
		SpringApplicationEvent event = getFinishedEvent(context, exception);
		if (context != null && context.isActive()) {
			// Listeners have been registered to the application context so we should
			// use it at this point if we can
			context.publishEvent(event);
		}
		else {
			// An inactive context may not have a multicaster so we use our multicaster to
			// call all of the context's listeners instead
			if (context instanceof AbstractApplicationContext) {
				for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
						.getApplicationListeners()) {
					this.initialMulticaster.addApplicationListener(listener);
				}
			}
			if (event instanceof ApplicationFailedEvent) {
				this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
			}
			this.initialMulticaster.multicastEvent(event);
		}
	}

	private SpringApplicationEvent getFinishedEvent(ConfigurableApplicationContext context, Throwable exception) {
		if (exception != null) {
			return new ApplicationFailedEvent(this.application, this.args, context, exception);
		}
		return new ApplicationReadyEvent(this.application, this.args, context);
	}

	private static class LoggingErrorHandler implements ErrorHandler {

		private static Log logger = LogFactory.getLog(EventPublishingRunListener.class);

		@Override
		public void handleError(Throwable throwable) {
			logger.warn("Error calling ApplicationEventListener", throwable);
		}

	}

}
